﻿// <copyright file="TeamAlertSubscriber.cs" company="http://www.dymetis.com">
//
// Copyright (c) 2011 Eddie Z. Lin.
//
// This source is subject to the Microsoft Public License.
// See http://www.opensource.org/licenses/ms-pl.
// All other rights reserved.
// 
// </copyright>

namespace Dymetis.TeamAlert
{
    using System;
    using System.Collections.Generic;
    using System.Configuration;
    using System.IO;
    using System.Reflection;
    using System.Xml.Linq;
    using Dymetis.Expressions;
    using Dymetis.TeamAlert.Configuration;
    using Microsoft.TeamFoundation.Build.Server;
    using Microsoft.TeamFoundation.Common;
    using Microsoft.TeamFoundation.Framework.Server;
    using Microsoft.TeamFoundation.WorkItemTracking.Server;

    public sealed class TeamAlertSubscriber : ISubscriber
    {
        private static readonly string name = typeof(TeamAlertSubscriber).FullName;

        private static readonly string assemblyName = Assembly.GetExecutingAssembly().GetName().Name + ".dll";

        private const string ConfigurationSectionName = "teamAlert";

        private readonly object syncRoot = new object();

        private static readonly Type[] subscribedTypes = new Type[]
        {
            ////typeof(CheckinNotification),
            ////typeof(ShelvesetNotification),

            ////typeof(BuildStatusChangeEvent),
            ////typeof(BuildResourceChangedEvent),
            ////typeof(BuildDefinitionUpgradeCompletionEvent),

            typeof(BuildCompletionNotificationEvent),

            typeof(WorkItemChangedEvent)
        };

        private TeamAlertConfigurationSection configuration;

        public string Name
        {
            get
            {
                return this.Name;
            }
        }

        public SubscriberPriority Priority
        {
            get
            {
                return SubscriberPriority.Normal;
            }
        }

        public EventNotificationStatus ProcessEvent(TeamFoundationRequestContext requestContext, NotificationType notificationType, object notificationEventArgs, out int statusCode, out string statusMessage, out ExceptionPropertyCollection properties)
        {
            statusCode = 0;
            properties = null;
            statusMessage = null;
            
            try
            {
                if (notificationType == NotificationType.Notification)
                {
                    if (this.EnsureConfigurationLoaded(requestContext))
                    {
                        var sender = new NotificationHelper(requestContext, this.configuration.AlertSettings);
                        var serializer = new EventXmlSerializer(requestContext);
                        var emailAddressHelper = new EmailAddressHelper(requestContext);

                        foreach (AlertConfigurationElement alert in this.configuration.Alerts)
                        {
                            switch ((NotificationEventType)Enum.Parse(typeof(NotificationEventType), alert.Event, true))
                            {
                                case NotificationEventType.WorkItemChangedEvent:
                                    this.ProcessWorkItemChangedEvent(requestContext, alert, notificationEventArgs, serializer, sender, emailAddressHelper);
                                    break;
                                case NotificationEventType.BuildCompletionEvent:
                                    this.ProcessBuildCompletionEvent(requestContext, alert, notificationEventArgs, serializer, sender, emailAddressHelper);
                                    break;
                                case NotificationEventType.BuildCompletionEvent2:
                                    this.ProcessBuildCompletionEvent2(requestContext, alert, notificationEventArgs, serializer, sender, emailAddressHelper);
                                    break;
                            }
                        }
                    }
                }
            }
            catch (Exception exception)
            {
                TeamFoundationApplication.LogException(requestContext, StringResources.UnexpectedError, exception);
            }

            return EventNotificationStatus.ActionPermitted;
        }

        public Type[] SubscribedTypes()
        {
            return subscribedTypes;
        }

        private static void ProcessEvent(TeamFoundationRequestContext requestContext, AlertConfigurationElement alert, NotificationHelper email, EmailAddressHelper addressHelper, XDocument eventDocument)
        {
            if (String.IsNullOrWhiteSpace(alert.FilterExpression) || 
                LogicalExpression.Parse(alert.FilterExpression, LogicalExpressionSyntax.ExtendedEventFilter).Evaluate(eventDocument.Root.AsParameterProvider()))
            {
                var sentRecipients = new HashSet<string>();

                foreach (RecipientConfigurationElement recipient in alert.Recipients)
                {
                    IList<AlertRecipient> recipients = null;

                    switch ((RecipientType)Enum.Parse(typeof(RecipientType), recipient.Type, true))
                    {
                        case RecipientType.DisplayNameField:
                            recipients = addressHelper.FindByDispalyName(eventDocument.Root, recipient.Address);
                            break;
                        case RecipientType.SidField:
                            recipients = addressHelper.FindBySid(eventDocument.Root, recipient.Address);
                            break;
                        case RecipientType.AccountNameField:
                            recipients = addressHelper.FindByAccountName(eventDocument.Root, recipient.Address);
                            break;
                        case RecipientType.EmailAddress:
                            recipients = new AlertRecipient[] 
                            {
                                new AlertRecipient() 
                                {
                                    EmailAddress = recipient.Address, 
                                    DisplayName = recipient.Address 
                                }
                            };
                            break;
                    }

                    if (recipients != null)
                    {
                        foreach (var r in recipients)
                        {
                            if (!sentRecipients.Contains(r.EmailAddress))
                            {
                                sentRecipients.Add(r.EmailAddress);
                                email.SendMailNotification(requestContext, eventDocument, r, recipient.AllowHtml, recipient.AttachRawEvent);
                            }
                        }
                    }
                }
            }
        }

        private bool EnsureConfigurationLoaded(TeamFoundationRequestContext requestContext)
        {
            if (this.configuration == null)
            {
                lock (this.syncRoot)
                {
                    if (this.configuration == null)
                    {
                        var configPath = Path.Combine(requestContext.ServiceHost.PlugInDirectory, assemblyName);
                        this.configuration = ConfigurationManager.OpenExeConfiguration(configPath).GetSection(ConfigurationSectionName) as TeamAlertConfigurationSection;
                    }
                }
            }

            return this.configuration != null;
        }

        private void ProcessWorkItemChangedEvent(TeamFoundationRequestContext requestContext, AlertConfigurationElement alert, object notificationEventArgs, EventXmlSerializer serializer, NotificationHelper email, EmailAddressHelper addressHelper)
        {
            if (notificationEventArgs is WorkItemChangedEvent)
            {
                XDocument eventDocument = serializer.Serialize<WorkItemChangedEvent>(notificationEventArgs);
                ProcessEvent(requestContext, alert, email, addressHelper, eventDocument);
            }
        }

        private void ProcessBuildCompletionEvent2(TeamFoundationRequestContext requestContext, AlertConfigurationElement alert, object notificationEventArgs, EventXmlSerializer serializer, NotificationHelper email, EmailAddressHelper addressHelper)
        {
            if (notificationEventArgs is BuildCompletionNotificationEvent)
            {
                XDocument eventDocument = serializer.Serialize<BuildCompletionEvent2>(notificationEventArgs);
                ProcessEvent(requestContext, alert, email, addressHelper, eventDocument);
            }
        }

        private void ProcessBuildCompletionEvent(TeamFoundationRequestContext requestContext, AlertConfigurationElement alert, object notificationEventArgs, EventXmlSerializer serializer, NotificationHelper email, EmailAddressHelper addressHelper)
        {
            if (notificationEventArgs is BuildCompletionNotificationEvent)
            {
                XDocument eventDocument = serializer.Serialize<BuildCompletionEvent>(notificationEventArgs);

                ProcessEvent(requestContext, alert, email, addressHelper, eventDocument);
            }
        }
    }
}
